/*
 * Copyright (C) 2008 Eric Bottard
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.code.liquidform.internal;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.cglib.proxy.Factory;

public class FrameworkObjectFactory {

	public static final FrameworkObjectFactory INSTANCE = new FrameworkObjectFactory();

	public <T> T makeNew(T instance) {
		Class<T> type = (Class<T>) instance.getClass();

		if (instance instanceof Factory) {
			Factory factory = (Factory) instance;
			return (T) factory.newInstance(factory.getCallbacks());
		} else {
			return makeNew(type);
		}
	}

	public <T> T makeNew(Class<T> type) {
		if (String.class.equals(type)) {
			return (T) new String();
		} else if (Integer.class.equals(type)) {
			return (T) new Integer(42);
		} else if (Date.class.equals(type)) {
			return (T) new Date();
		} else if (Long.class.equals(type)) {
			return (T) new Long(42);
		} else if (Short.class.equals(type)) {
			return (T) new Short((short) 42);
		} else if (Double.class.equals(type)) {
			return (T) new Double(666);
		} else if (Float.class.equals(type)) {
			return (T) new Float(42);
		} else if (BigInteger.class.equals(type)) {
			return (T) new BigInteger("42");
		} else if (BigDecimal.class.equals(type)) {
			return (T) new BigDecimal(42);
		} else if (Boolean.class.equals(type)) {
			return (T) new Boolean(true);
		} else if (List.class.isAssignableFrom(type)) {
			return (T) new ArrayList(0);
		} else if (Set.class.isAssignableFrom(type)) {
			return (T) new HashSet(0);
		} else if (Map.class.isAssignableFrom(type)) {
			return (T) new HashMap();
		} else if (Enum.class.isAssignableFrom(type)) {
			// return any enum of this enum type
			return fakeInstanceForEnum(type);
		} else {
			throw new UnsupportedOperationException(
					"Can't create object of type "
							+ type
							+ ". This may happen if you forgot to annotate a persistent class with @Entity");
		}
	}

	// taken from http://www.theserverside.com/news/thread.tss?thread_id=50190
	private <T> T fakeInstanceForEnum(Class<T> type) {
		try {
			Constructor con = type.getDeclaredConstructors()[0];
			Method[] methods = con.getClass().getDeclaredMethods();
			for (Method m : methods) {
				if (m.getName().equals("acquireConstructorAccessor")) {
					m.setAccessible(true);
					m.invoke(con, new Object[0]);
				}
			}
			Field[] fields = con.getClass().getDeclaredFields();
			Object ca = null;
			for (Field f : fields) {
				if (f.getName().equals("constructorAccessor")) {
					f.setAccessible(true);
					ca = f.get(con);
				}
			}
			Method m = ca.getClass().getMethod("newInstance",
					new Class[] { Object[].class });
			m.setAccessible(true);

			T v = (T) m.invoke(ca, new Object[] { new Object[] { "dummy",
					Integer.MAX_VALUE } });
			return v;
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
}
